/*
 *  desktop.cc
 *  wonderland-project
 *
 *  Created by Ben Nolan on 20/10/08.
 *  Copyright 2008 Nolan Consulting Limited. All rights reserved.
 *
 */

#include "desktop.h"

#include <Ogre.h>
#include <CEGUI/CEGUIWindowManager.h>
#include <CEGUI/CEGUIWindow.h>
#include <CEGUI/elements/CEGUIPushButton.h>
#include <CEGUI/elements/CEGUIEditBox.h>
#include <iostream>
#include <fstream>
#include <boost/bind.hpp>

#include "wonderland.h"

#include "v8defines.h"
#include "scripting_context.h"
#include "gui.h"

namespace JSWidget{
  v8::Handle<v8::ObjectTemplate> instance_template;
  v8::Handle<v8::FunctionTemplate> function_template;
  v8::Persistent<v8::Context> context_;
  
  Wonderland::Gui *gui_;

  bool onClick(v8::Persistent<v8::Function> func, const CEGUI::EventArgs& e){
    context_->Enter();
    func->Call(context_->Global(), 0, NULL);
    return true;
  }
  
  V8CALLBACK(Window, _connectEvent){
    if(args.Length()!=2)
      return ThrowException(v8::String::New("Invalid argument"));

    if (args[1].IsEmpty() || !args[1]->IsFunction()){
      return ThrowException(v8::String::New("Invalid argument"));
    }

    v8::String::Utf8Value event(args[0]);
    v8::Persistent<v8::Function> func = v8::Persistent<v8::Function>::New(
      v8::Local<v8::Function>::Cast(args[1])
    );
  
    CEGUI::String ce_event;
    
    if(*event == (std::string) "click"){
      ce_event = CEGUI::PushButton::EventClicked;
    }else if(*event == (std::string) "submit"){
      ce_event = CEGUI::Editbox::EventTextAccepted;
    }else{
      return ThrowException(v8::String::New("Invalid event argument"));
    }
    
    V8INTERNAL(CEGUI::Window*)->subscribeEvent(
			ce_event, 
			CEGUI::Event::Subscriber(boost::bind(&onClick, func, _1))
    );

    return v8::Undefined();
  }

  V8_GETTER(Widget, text){
    return v8::String::New(V8INTERNAL(CEGUI::Window*)->getText().c_str());
  }
  
  V8_SETTER(Widget, text){
    v8::String::Utf8Value str(value);
    V8INTERNAL(CEGUI::Window*)->setText((std::string) *str);
  }
  
  // Internal function
  V8CALLBACK(Widget,create){
    v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.Holder());
    return self;
  }

  /* Sets internal fields with our idiom of storing the class name in the second field */
  void setInternalFields(v8::Persistent<v8::Object> p, CEGUI::Window* win){
    p->SetInternalField(0, v8::External::New(win));
    p->SetInternalField(1, v8::String::New("Widget"));
  }

  /* Called from c++ */
  v8::Handle<v8::Value> New(CEGUI::Window* win){
    context_->Enter();
    v8::Persistent<v8::Object> instance = v8::Persistent<v8::Object>::New(function_template->GetFunction()->NewInstance());
    setInternalFields(instance, win);
    return instance;
  }

  void Initialize(Wonderland::ScriptingContext *sc, Wonderland::Gui *gui){
    context_ = sc->getContext();
    gui_ = gui;

    // Function Template
    function_template = v8::FunctionTemplate::New(Widget_create);
    function_template->SetClassName(v8::String::New("Widget"));

    // Instance template
    instance_template = function_template->InstanceTemplate();
    instance_template->SetInternalFieldCount(2);

    instance_template->SetAccessor(v8::String::New("text"), Widget_gettext, Widget_settext);

    instance_template->Set("_connectEvent", v8::FunctionTemplate::New(Window__connectEvent));
  }
}

namespace JSWindow{
  v8::Handle<v8::ObjectTemplate> instance_template;
  v8::Handle<v8::FunctionTemplate> function_template;
  v8::Persistent<v8::Context> context_;
  
  Wonderland::Gui *gui_;

  // Close the window
  V8CALLBACK(Window,close){
    V8INTERNAL(CEGUI::Window*)->hide();
    return v8::Undefined();
  }
  
  // Hide the window
  V8CALLBACK(Window,hide){
    V8INTERNAL(CEGUI::Window*)->hide();
    return v8::Undefined();
  }
  
  // Show the window
  V8CALLBACK(Window,show){
    V8INTERNAL(CEGUI::Window*)->show();
    return v8::Undefined();
  }
  
  // Get a widget by id
  V8CALLBACK(Window,getWidgetById){
    if(args.Length()!=1)
      return ThrowException(v8::String::New("Invalid argument"));

    v8::String::Utf8Value id(args[0]);

    if(!CEGUI::WindowManager::getSingleton().isWindowPresent((CEGUI::utf8*)*id)){
      return v8::False();
    }
    
    return JSWidget::New(CEGUI::WindowManager::getSingleton().getWindow((CEGUI::utf8*)*id));
  }
  
  // Internal function
  V8CALLBACK(Window,create){
    v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.Holder());
    return self;
  }

  /* Sets internal fields with our idiom of storing the class name in the second field */
  void setInternalFields(v8::Persistent<v8::Object> p, CEGUI::Window* win){
    p->SetInternalField(0, v8::External::New(win));
    p->SetInternalField(1, v8::String::New("Window"));
  }

  /* Called from c++ */
  v8::Handle<v8::Value> New(CEGUI::Window* win){
    context_->Enter();
    v8::Persistent<v8::Object> instance = v8::Persistent<v8::Object>::New(function_template->GetFunction()->NewInstance());
    setInternalFields(instance, win);
    return instance;
  }

  void Initialize(Wonderland::ScriptingContext *sc, Wonderland::Gui *gui){
    context_ = sc->getContext();
    gui_ = gui;

    // Function Template
    function_template = v8::FunctionTemplate::New(Window_create);
    function_template->SetClassName(v8::String::New("Window"));

    // Instance template
    instance_template = function_template->InstanceTemplate();
    instance_template->SetInternalFieldCount(2);

    instance_template->Set("getWidgetById", v8::FunctionTemplate::New(Window_getWidgetById));
    instance_template->Set("close", v8::FunctionTemplate::New(Window_close));
    instance_template->Set("hide", v8::FunctionTemplate::New(Window_hide));
    instance_template->Set("show", v8::FunctionTemplate::New(Window_show));
  }
}

namespace JSDesktop{
  v8::Handle<v8::ObjectTemplate> instance_template;
  v8::Handle<v8::FunctionTemplate> function_template;
  v8::Persistent<v8::Context> context_;
  
  Wonderland::Gui *gui_;

  V8CALLBACK(Desktop,createWindow){
    if(args.Length()!=1)
      return ThrowException(v8::String::New("Invalid argument"));

    v8::String::Utf8Value xml(args[0]);
      
	std::string temporary_file = Wonderland::Locations::Resources() + "gui/temporary.layout";
  	std::string script = *xml;

   	std::fstream fs(temporary_file.c_str(), std::fstream::out);
  	fs << script;
  	fs.close();

	CEGUI::Window* win;

	try{
	 	win = CEGUI::WindowManager::getSingleton().loadWindowLayout((CEGUI::utf8*)"temporary.layout");
	}catch(...){
		return ThrowException(v8::String::New("XML not valid"));
	}

 	gui_->getRootWindow()->addChildWindow(win);

    return JSWindow::New(win);
  }
  
  /* Construct a new node - doesnt actually allocate the node, use world.createBone for that */
  V8CALLBACK(Desktop,create){
    v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.Holder());
    return self;
  }

  /* Only called from c++ */
  v8::Handle<v8::Value> New(){
    context_->Enter();
    v8::Persistent<v8::Object> instance = v8::Persistent<v8::Object>::New(function_template->GetFunction()->NewInstance());
    return instance;
  }

  void Initialize(Wonderland::ScriptingContext *sc, Wonderland::Gui *gui){
    JSWindow::Initialize(sc, gui);
    JSWidget::Initialize(sc, gui);

    context_ = sc->getContext();
    gui_ = gui;

    // Function Template
    function_template = v8::FunctionTemplate::New(Desktop_create);
    function_template->SetClassName(v8::String::New("Desktop"));

    // Instance template
    instance_template = function_template->InstanceTemplate();

    #define V8REGISTER(function) instance_template->Set(#function, v8::FunctionTemplate::New(Desktop_ ## function));
    V8REGISTER(createWindow);
  }
}

